#include "wmutils.h"

namespace Utils {

static Atom atoms[ATOM_COUNT];
static bool atomsCreated = false;
};

bool Utils::createNetWMAtoms(Display *d)
{
	if (atomsCreated)
		return true;
	
    static const char *atomNames[] = {
        "COMPOSITING_MANAGER",
        "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR",
        "KWM_WIN_ICON",
        "_MOTIF_WM_HINTS",
        "_MOTIF_WM_INFO",
        "_NET_ACTIVE_WINDOW",
        "_NET_CLIENT_LIST",
        "_NET_CLIENT_LIST_STACKING",
        "_NET_CLOSE_WINDOW",
        "_NET_CURRENT_DESKTOP",
        "_NET_DESKTOP_GEOMETRY",
        "_NET_DESKTOP_LAYOUT",
        "_NET_DESKTOP_NAMES",
        "_NET_DESKTOP_VIEWPORT",
        "_NET_FRAME_EXTENTS",
        "_NET_MOVERESIZE_WINDOW",
        "_NET_NUMBER_OF_DESKTOPS",
        "_NET_REQUEST_FRAME_EXTENTS",
        "_NET_SHOWING_DESKTOP",
        "_NET_STARTUP_ID",
        "_NET_SUPPORTED",
        "_NET_SUPPORTING_WM_CHECK",
        "_NET_SYSTEM_TRAY_OPCODE",
        "_NET_SYSTEM_TRAY_MESSAGE_DATA",
        "_NET_WM_ACTION_ABOVE",
        "_NET_WM_ACTION_BELOW",
        "_NET_WM_ACTION_CHANGE_DESKTOP",
        "_NET_WM_ACTION_CLOSE",
        "_NET_WM_ACTION_FULLSCREEN",
        "_NET_WM_ACTION_MAXIMIZE_HORZ",
        "_NET_WM_ACTION_MAXIMIZE_VERT",
        "_NET_WM_ACTION_MINIMIZE",
        "_NET_WM_ACTION_MOVE",
        "_NET_WM_ACTION_RESIZE",
        "_NET_WM_ACTION_SHADE",
        "_NET_WM_ACTION_STICK",
        "_NET_WM_ALLOWED_ACTIONS",
        "_NET_WM_CONTEXT_HELP",
        "_NET_WM_DESKTOP",
        "_NET_WM_ICON",
        "_NET_WM_ICON_GEOMETRY",
        "_NET_WM_ICON_NAME",
        "_NET_WM_MOVERESIZE",
        "_NET_WM_NAME",
        "_NET_WM_PID",
        "_NET_WM_PING",
        "_NET_WM_WINDOW_OPACITY",
        "_NET_WM_WINDOW_OPACITY_LOCKED",
        "_NET_WM_STATE",
        "_NET_WM_STATE_ABOVE",
        "_NET_WM_STATE_BELOW",
        "_NET_WM_STATE_DEMANDS_ATTENTION",
        "_NET_WM_STATE_FULLSCREEN",
        "_NET_WM_STATE_HIDDEN",
        "_NET_WM_STATE_MAXIMIZED_HORZ",
        "_NET_WM_STATE_MAXIMIZED_VERT",
        "_NET_WM_STATE_MODAL",
        "_NET_WM_STATE_SHADED",
        "_NET_WM_STATE_SKIP_PAGER",
        "_NET_WM_STATE_SKIP_TASKBAR",
        "_NET_WM_STATE_STICKY",
        "_NET_WM_STRUT",
        "_NET_WM_STRUT_PARTIAL",
        "_NET_WM_SYNC_REQUEST",
        "_NET_WM_SYNC_REQUEST_COUNTER",
        "_NET_WM_USER_TIME",
        "_NET_WM_USER_TIME_WINDOW",
        "_NET_WM_WINDOW_TYPE",
        "_NET_WM_WINDOW_TYPE_DESKTOP",
        "_NET_WM_WINDOW_TYPE_DIALOG",
        "_NET_WM_WINDOW_TYPE_DOCK",
        "_NET_WM_WINDOW_TYPE_MENU",
        "_NET_WM_WINDOW_TYPE_NORMAL",
        "_NET_WM_WINDOW_TYPE_SPLASH",
        "_NET_WM_WINDOW_TYPE_TOOLBAR",
        "_NET_WM_WINDOW_TYPE_UTILITY",
        "_NET_WORKAREA",
        "SM_CLIENT_ID",
        "UTF8_STRING",
        "WM_CHANGE_STATE",
        "WM_CLIENT_LEADER",
        "WM_CLIENT_MACHINE",
        "WM_COLORMAP_WINDOWS",
        "WM_DELETE_WINDOW",
        "WM_HINTS",
        "WM_NORMAL_HINTS",
        "WM_PROTOCOLS",
        "WM_STATE",
        "WM_TAKE_FOCUS",
        "WM_TRANSIENT_FOR",
        "WM_WINDOW_ROLE",
        "XFWM4_COMPOSITING_MANAGER",
        "XFWM4_TIMESTAMP_PROP",
        "_XROOTPMAP_ID",
        "_XSETROOT_ID",
		"MANAGER",

		"_WIN_HINTS",
		"_WIN_LAYER",
		"_WIN_STATE",
		"_WIN_PROTOCOLS"

    };

    return (XInternAtoms (d, (char **) atomNames, ATOM_COUNT, FALSE, atoms) != 0);
}

Atom Utils::atom(int a)
{
	if ((a >= 0) || (a <= ATOM_COUNT)) 
		return atoms[a];
	else
		return None;
}

int Utils::windowType(Window w)
{
	Atom *state;
	int num3;
	int t = Unknown;
	
	if (!(state = (Atom *) getXAProperty(w, atoms[NET_WM_WINDOW_TYPE], XA_ATOM, &num3)))
		return t;
	while (--num3 >= 0) {
		if (state[num3] == atoms[NET_WM_WINDOW_TYPE_DESKTOP]) t = Desktop;
		else if (state[num3] == atoms[NET_WM_WINDOW_TYPE_DOCK]) t = Dock;
		else if (state[num3] == atoms[NET_WM_WINDOW_TYPE_TOOLBAR]) t = Toolbar;
		else if (state[num3] == atoms[NET_WM_WINDOW_TYPE_MENU]) t = Menu;
		else if (state[num3] == atoms[NET_WM_WINDOW_TYPE_UTILITY]) t = Utility;
		else if (state[num3] == atoms[NET_WM_WINDOW_TYPE_SPLASH]) t = Splash;
		else if (state[num3] == atoms[NET_WM_WINDOW_TYPE_DIALOG]) t = Dialog;
		else if (state[num3] == atoms[NET_WM_WINDOW_TYPE_NORMAL]) t = Normal;
	}
	XFree(state);
	return t;
}

// send protocol message to child window
void Utils::sendWMProtocols(Window w, long data0, long data1)
{
    XClientMessageEvent ev;

    //memset(&ev, 0, sizeof(ev));
    ev.type = ClientMessage;
    ev.window = w;
    ev.message_type = atom(WM_PROTOCOLS);
    ev.format = 32;
	ev.send_event = TRUE;
    ev.data.l[0] = data0;
    ev.data.l[1] = data1;

    XSendEvent(display(), w, FALSE, 0L, (XEvent*) &ev);
}

bool Utils::checkTypeAndFormat(int expectedFormat, Atom expectedType, int nitems, int format, Atom type)
{
	if ((expectedFormat == format) && (expectedType == type) && (nitems != 0))
		return true;
	else
		return false;
}


QString Utils::getUTF8String(Display *dpy, Window w, int atomId)
{
	Atom type;
	int format;
	unsigned long bytes_after=0;
	unsigned long n_items=0;

	unsigned char *str_p = NULL;

	if ((XGetWindowProperty(dpy, w, atom(atomId), 0, LONG_MAX, FALSE,  atom(UTF8_STRING),
		&type, &format, &n_items, &bytes_after, &str_p) != Success) || (type == None)) {

		//qDebug() << "No UTF8_STRING property found";
		return QString("");
	}

	if (!checkTypeAndFormat(8, atom(UTF8_STRING), -1, format, type)) {
		//qDebug() << "UTF8_STRING format invalid";
		if (str_p) XFree(str_p);
		return QString("");
	}

	QString s = QString::fromUtf8((char *)str_p);
	if (str_p) XFree(str_p);
	return s;
}

QString Utils::getTextProperty(Display *dpy, Window w, Atom  _atom)
{
	XTextProperty text;
	char **list;
	int count;
	QString s;

	text.nitems = 0;
	if (XGetTextProperty(dpy, w, &text, _atom)) {
		if (XmbTextPropertyToTextList(dpy, &text, &list, &count) == Success && count && list) {
			s = QString::fromUtf8(list[0]);
		}
		if (list) XFreeStringList(list);
	} else {
		s = "";
	}

	if ((text.value) && (text.nitems > 0)) {
		XFree(text.value);
	}

	return s;
}

void *Utils::getXAProperty(Window win, Atom prop, Atom type, int *nitems)
{
    Atom typeRet;
    int formatRet;
    unsigned long itemsRet;
    unsigned long afterRet;
    unsigned char *propData;

    propData = NULL;

    if (XGetWindowProperty(display(), win, prop, 0, 0x7fffffff, False,
            type, &typeRet, &formatRet, &itemsRet,
            &afterRet, &propData) != Success)
        return NULL;

    if (nitems)
        *nitems = itemsRet;
    return propData;
}

bool Utils::getWindowProperty(Display *dpy, Window window, int atomId, Window *w)
{
	Atom type;
	int format;
	unsigned long nitems;
	unsigned long bytes_after;
	unsigned char *prop;

	*w = None;
	if (XGetWindowProperty(dpy, window,  atom(atomId),
		0L, 1L, FALSE, XA_WINDOW, &type, &format, &nitems,
		&bytes_after, (unsigned char **) &prop) == Success)
	{
		if (prop) {
			*w = *((Window *) prop);
			XFree(prop);
		}
		if (!prop || checkTypeAndFormat(32, XA_WINDOW, -1, format, type))
		{
			*w = None;
			return false;
		}
	}

	return true;
}

bool Utils::getHint(Display *dpy, Window window, int atomId, long *value)
{
	Atom type;
	int format;
	unsigned long items_read, items_left;
	unsigned char *data;

	if ((XGetWindowProperty(dpy, window, atom(atomId),
		0L, 1L, FALSE, XA_CARDINAL, &type, &format, &items_read, &items_left,
		(unsigned char **) &data) == Success) && (items_read))
	{
		*value = *((long *) data);
		if (data) {
			XFree(data);
		}
		return true;
	}
	return false;
}


bool Utils::find_largest_size(unsigned long *data, unsigned long nitems, int *width, int *height)
{

    *width = 0; *height = 0;

    while (nitems > 0) {
        int w, h;

        if (nitems < 3)
            return false;

        w = data[0];
        h = data[1];

        if (nitems < ((w *h) + 2))
            return false;

        *width = qMax(w, *width);
        *height = qMax(h, *height);

        data += (w * h) + 2;
        nitems -= (w * h) + 2;
    }
    return true;
}

// stolen (like many others) from xfwm4
bool Utils::find_best_size (unsigned long *data, unsigned long nitems, int pref_width, int pref_height,
        int *width, int *height, unsigned long **start)
{
    unsigned long *best_start;
    int pref_size;
    int w, h, best_size, this_size;
    int best_w, best_h, max_width, max_height;

    *width = 0;
    *height = 0;
    *start = NULL;

    if (!find_largest_size (data, nitems, &max_width, &max_height)) {
        return false;
    }

    if (pref_width < 0) {
        pref_width = max_width;
    }

    if (pref_height < 0) {
        pref_height = max_height;
    }

    best_w = 0;
    best_h = 0;
    best_start = NULL;

    while (nitems > 0)
    {
        bool replace;

        replace = false;

        if (nitems < 3) {
            return FALSE;       /* no space for w, h */
        }

        w = data[0];
        h = data[1];

        if (nitems < ((w * h) + 2)) {
            break;              /* not enough data */
        }

        if (best_start == NULL) {
            replace = true;
        } else {
            /* work with averages */
            pref_size = (pref_width + pref_height) / 2;
            best_size = (best_w + best_h) / 2;
            this_size = (w + h) / 2;

            if ((best_size < pref_size) && (this_size >= pref_size)) {
                /* larger than desired is always better than smaller */
                replace = true;
            } else if ((best_size < pref_size) && (this_size > best_size)) {
                /* if we have too small, pick anything bigger */
                replace = true;
            } else if ((best_size > pref_size) && (this_size >= pref_size) && (this_size < best_size)) {
                /* if we have too large, pick anything smaller but still >= the ideal */
                replace = true;
            }
        }

        if (replace) {
            best_start = data + 2;
            best_w = w;
            best_h = h;
        }

        data += (w * h) + 2;
        nitems -= (w * h) + 2;
    }

    if (best_start)
    {
        *start = best_start;
        *width = best_w;
        *height = best_h;
        return true;
    }

    return false;
}

void argb_data_to_pixdata(unsigned long *argb_data, int len, unsigned char **pixdata)
{
    unsigned char *p;
    unsigned int argb, rgba;

    *pixdata = new unsigned char[len * 4];
    p = *pixdata;

    int i = 0;
    while (i < len) {
        argb = argb_data[i];
        rgba = (argb << 8) | (argb >> 24);

        *p = rgba >> 24; ++p;
        *p = (rgba >> 16) & 0xff; ++p;
        *p = (rgba >> 8) & 0xff; ++p;
        *p = rgba & 0xff; ++p;

        ++i;
    }
}

bool Utils::readRGBIcon(Display *dpy, Window window, int pref_width, int pref_height,
        int *width, int *height, unsigned char **pixdata)
{
    Atom type;
    int format;
    unsigned long bytes_after;

    unsigned long nitems;
    unsigned long *data;
    unsigned long *best;
    int w, h;

    type = None;
    data = NULL;

    if (XGetWindowProperty(dpy, window, atom(NET_WM_ICON), 0, LONG_MAX, FALSE, XA_CARDINAL,
        &type, &format, &nitems, &bytes_after, (unsigned char **)&data) != Success)
    {
        *data = NULL;
        return false;
    }

    if (type != XA_CARDINAL) {
        XFree(data);
        return false;
    }

    if (!find_best_size(data, nitems, pref_width, pref_height, &w, &h, &best)) {
        XFree(data);
        return false;
    }

    *width = w;
    *height = h;
    argb_data_to_pixdata(best, w * h, pixdata);

    XFree(data);
    return true;
}
